﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Concat
{
    [System.Diagnostics.DebuggerNonUserCode]
    class CallStack
    {
        Location loc;
        Stack<CallFrame> frames;

        public CallStack()
        {
            frames = new Stack<CallFrame>();
        }

        public CallFrame[] getCallFrames()
        {
            return frames.ToArray();
        }

        public bool hasFrames()
        {
            return frames.Count > 0;
        }

        public bool hasarg(string arg)
        {
            foreach (CallFrame frame in frames)
                if (frame.hasarg(arg))
                    return true;
            return false;
        }
        public object getarg(string arg)
        {
            foreach (CallFrame frame in frames)
                if (frame.hasarg(arg))
                    return frame.getarg(arg);
            throw new Exception("Should have checked hasarg() first!");
        }

        public string[] getargs()
        {
            if (frames.Count > 0)
                return top().getargs();
            return new string[0];
        }

        public bool hasDynamicWord(string word)
        {
            bool top = true;
            foreach (CallFrame frame in frames)
            {
                if (frame.ignoresDynamicWord(word))
                    return false;
                if (frame.hasDynamicWord(word) && !top)
                    return true;
                top = false;
            }
            return false;
        }
        public Word getDynamicWord(string word)
        {
            foreach (CallFrame frame in frames)
                if (frame.hasDynamicWord(word))
                    return frame.getDynamicWord(word);
            throw new Exception("Should have checked CallStack.hasDynamicWord(string) first...");
        }
        public void setDynamicWord(string name, Word word)
        {
            frames.Peek().setDynamicWord(name, word);
        }

        public CallFrame top()
        {
            return frames.Peek();
        }
        public Location getLoc()
        {
            if (frames.Count == 0)
                return loc;
            return top().loc;
        }

        public Location getProgLoc()
        {
            return loc;
        }
        public void setProgLoc(Location loc)
        {
            this.loc = loc;
        }

        public void call(CallFrame frame)
        {
            frames.Push(frame);
        }
        public void ret()
        {
            frames.Pop().ret();
        }

        public int depth()
        {
            return frames.Count;
        }

        public override string ToString()
        {
            string bottom = "main at " + loc;
            return frames.map(frame => frame.ToString() + "\n").concat() + bottom;
        }

        public CallStack copy()
        {
            CallStack cs = new CallStack();
            cs.loc = new Location(loc.file, loc.pos, loc.line, loc.col);
            cs.frames = new Stack<CallFrame>(frames);
            return cs;
        }
    }

    [System.Diagnostics.DebuggerNonUserCode]
    class CallFrame : IHasEvent
    {
        public string name;
        public int index;
        public Term[] terms;
        Dictionary<string, object> args;
        Dictionary<string, Word> dynamicVocab;
        HashSet<string> ignoredDynamics;
        public Quote handler;

        public bool stopExecution = false;
        public event Action OnReturn;

        public CallFrame(string name, Term[] terms, HashSet<string> ignoredDynamics, Quote handler)
        {
            this.name = name;
            this.index = 0;
            this.terms = terms;
            this.args = new Dictionary<string, object>();
            this.dynamicVocab = new Dictionary<string, Word>();
            this.ignoredDynamics = ignoredDynamics;
            this.handler = handler;
        }

        public void addCallback(Action action)
        {
            OnReturn += action;
        }
        public void ret()
        {
            if (OnReturn != null)
                OnReturn();
        }

        public void setarg(string arg, object value)
        {
            args[arg] = value;
        }

        public bool hasDynamicWord(string ident)
        {
            return dynamicVocab.ContainsKey(ident);// && !ignoredDynamics.Contains(ident);
        }
        public bool ignoresDynamicWord(string ident)
        {
            return ignoredDynamics.Contains(ident);
        }
        public Word getDynamicWord(string ident)
        {
            return dynamicVocab[ident];
        }
        public void setDynamicWord(string name, Word word)
        {
            dynamicVocab[name] = word;
        }

        public bool hasarg(string arg)
        {
            return args.ContainsKey(arg);
        }
        public object getarg(string arg)
        {
            return args[arg];
        }

        public string[] getargs()
        {
            return args.Keys.ToArray();
        }

        public Location loc
        {
            get
            {
                return terms[index == terms.Length 
                           ? terms.Length - 1 
                           : index].loc;
            }
        }

        public override string ToString()
        {
            string str = name + "(" + args.Keys.interleave(",").concat() + ")";
            Location loc = this.loc;
            if (loc != null)
                str += " at " + loc;
            return str;
        }
    }
}
